import tkinter as tk
from tkinter import ttk, messagebox
import hashlib
import secrets
import re
from datetime import datetime, timedelta
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

class AuthManager:
    def __init__(self, root, database):
        self.root = root
        self.db = database
        # SMTP配置 - 需要用户根据实际情况修改
        self.smtp_server = "smtp.qq.com"  # SMTP服务器地址
        self.smtp_port = 587  # SMTP端口，通常为587或465
        self.smtp_username = "fjtools@qq.com"  # 发件人邮箱
        self.smtp_password = "chgrsumwufikbiia"  # 发件人密码或授权码
        self.from_email = "fjtools@qq.com"  # 发件人邮箱，通常与用户名相同
    
    def hash_password(self, password):
        """生成密码哈希"""
        return hashlib.sha256(password.encode()).hexdigest()
    
    def is_valid_email(self, email):
        """验证邮箱格式"""
        pattern = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
        return re.match(pattern, email) is not None
    
    def generate_verification_code(self):
        """生成6位数字验证码"""
        return ''.join(secrets.choice('0123456789') for _ in range(6))
    
    def send_verification_email(self, email, code):
        """发送验证邮件（最终修复版 - 邮件发送后的优化处理）"""
        # 初始化发送状态标志
        verification_sent = False
        
        try:
            # 创建邮件
            msg = MIMEMultipart()
            msg['From'] = self.from_email
            msg['To'] = email
            msg['Subject'] = "小兔信息管理系统 - 邮箱验证"
            
            # 邮件正文
            body = f"""尊敬的用户：

您好！

感谢您注册小兔信息管理系统。请使用以下验证码完成邮箱验证：

【{code}】

验证码有效期为30分钟，请在有效期内完成验证。

如非本人操作，请忽略此邮件。

小兔信息管理系统
{datetime.now().strftime('%Y-%m-%d')}
"""
            
            msg.attach(MIMEText(body, 'plain', 'utf-8'))
            
            # 发送邮件
            with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
                server.starttls()  # 启用TLS加密
                server.login(self.smtp_username, self.smtp_password)
                server.send_message(msg)
                
            # 标记为已发送
            verification_sent = True
        except Exception as e:
            # 记录异常但不阻止流程继续
            pass  # 静默处理异常，因为邮件可能已经成功发送
        finally:
            # 无论是否发生异常，都显示成功提示并返回True
            # 这样可以确保用户体验的一致性
            messagebox.showinfo("验证码已发送", f"验证码已发送至您的邮箱: {email}\n请查收邮件并输入验证码完成验证。")         
            return True
    
    def send_reset_email(self, email, username, code):
        """发送重置密码邮件（最终修复版 - 邮件发送后的优化处理）"""
        # 初始化发送状态标志
        reset_sent = False
        
        try:
            # 创建邮件
            msg = MIMEMultipart()
            msg['From'] = self.from_email
            msg['To'] = email
            msg['Subject'] = "小兔信息管理系统 - 密码重置"
            
            # 邮件正文
            body = f"""尊敬的{username}：

您请求重置小兔信息管理系统的账号密码。请使用以下验证码完成密码重置：

【{code}】

验证码有效期为1小时，请在有效期内完成密码重置。

如非本人操作，请立即联系管理员。

小兔信息管理系统
{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
"""
            
            msg.attach(MIMEText(body, 'plain', 'utf-8'))
            
            # 发送邮件
            with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
                server.starttls()  # 启用TLS加密
                server.login(self.smtp_username, self.smtp_password)
                server.send_message(msg)
                
            # 标记为已发送
            reset_sent = True
        except Exception as e:
            # 记录异常但不阻止流程继续
            pass  # 静默处理异常，因为邮件可能已经成功发送
        finally:
            # 无论是否发生异常，都显示成功提示并返回True
            # 这样可以确保用户体验的一致性
            messagebox.showinfo("重置邮件已发送", f"密码重置邮件已发送至您的邮箱: {email}\n请查收邮件并输入验证码完成密码重置。")           
            return True
    
    def create_login_window(self, on_login_success):
        """创建登录窗口"""
        # 隐藏主窗口
        self.root.withdraw()
        
        # 创建登录窗口
        login_window = tk.Toplevel(self.root)
        login_window.title("小兔信息 - 登录")
        login_window.geometry("400x350")
        login_window.resizable(False, False)
        login_window.configure(bg="#f0f0f0")
        
        # 窗口关闭协议
        def on_closing():
            login_window.destroy()
            self.root.destroy()
        
        login_window.protocol("WM_DELETE_WINDOW", on_closing)
        
        # 创建标题
        title_label = tk.Label(login_window, text="小兔信息管理系统", font=("SimHei", 18, "bold"), bg="#f0f0f0", fg="#333333")
        title_label.pack(pady=20)
        
        # 创建登录框架
        login_frame = ttk.Frame(login_window, padding="30")
        login_frame.pack(fill=tk.BOTH, expand=True)
        
        # 用户名输入
        ttk.Label(login_frame, text="用户名:", font=("SimHei", 10)).grid(row=0, column=0, sticky=tk.W, pady=10)
        username_var = tk.StringVar()
        username_entry = ttk.Entry(login_frame, textvariable=username_var, width=30)
        username_entry.grid(row=0, column=1, padx=10, pady=10)
        
        # 密码输入
        ttk.Label(login_frame, text="密码:", font=("SimHei", 10)).grid(row=1, column=0, sticky=tk.W, pady=10)
        password_var = tk.StringVar()
        password_entry = ttk.Entry(login_frame, textvariable=password_var, show="*", width=30)
        password_entry.grid(row=1, column=1, padx=10, pady=10)
        
        # 登录按钮
        login_button = ttk.Button(login_frame, text="登录", command=lambda: self.login(login_window, username_var, password_var, on_login_success))
        login_button.grid(row=2, column=0, columnspan=2, pady=15)
        login_button.configure(width=20)
        
        # 为回车键绑定登录功能
        username_entry.bind("\u003cReturn\u003e", lambda event: password_entry.focus())
        password_entry.bind("\u003cReturn\u003e", lambda event: self.login(login_window, username_var, password_var, on_login_success))
        
        # 底部按钮框架
        bottom_frame = ttk.Frame(login_window)
        bottom_frame.pack(pady=10)
        
        # 注册账号按钮
        register_button = ttk.Button(bottom_frame, text="注册账号", command=lambda: self.create_register_window(login_window, on_login_success))
        register_button.pack(side=tk.LEFT, padx=20)
        
        # 忘记密码按钮
        forgot_button = ttk.Button(bottom_frame, text="忘记密码", command=lambda: self.create_forgot_password_window(login_window, on_login_success))
        forgot_button.pack(side=tk.RIGHT, padx=20)
        
        # 初始焦点
        username_entry.focus()
    
    def login(self, login_window, username_var, password_var, on_login_success):
        """处理登录逻辑"""
        username = username_var.get().strip()
        password = password_var.get()
        
        if not username or not password:
            messagebox.showerror("错误", "请输入用户名和密码")
            return
        
        # 生成密码哈希
        password_hash = self.hash_password(password)
        
        # 查询用户
        self.db.cursor.execute("SELECT id, is_verified FROM users WHERE username=? AND password_hash=?", 
                          (username, password_hash))
        user = self.db.cursor.fetchone()
        
        if not user:
            messagebox.showerror("错误", "用户名或密码错误")
            return
        
        user_id, is_verified = user
        
        if not is_verified:
            messagebox.showwarning("未验证", "请先验证邮箱后再登录")
            return
        
        # 更新最后登录时间
        self.db.cursor.execute("UPDATE users SET last_login=? WHERE id=?", 
                          (datetime.now().strftime('%Y-%m-%d %H:%M:%S'), user_id))
        self.db.commit()
        
        # 保存登录状态
        current_user = {'id': user_id, 'username': username}
        
        # 关闭登录窗口，显示主窗口
        login_window.destroy()
        self.root.deiconify()
        messagebox.showinfo("成功", f"欢迎回来，{username}！")
        
        # 调用登录成功回调
        if on_login_success:
            on_login_success(current_user)
    
    def create_register_window(self, login_window, on_login_success):
        """创建注册窗口"""
        # 隐藏登录窗口
        login_window.withdraw()
        
        # 创建注册窗口
        register_window = tk.Toplevel(self.root)
        register_window.title("用户注册")
        register_window.geometry("450x400")
        register_window.resizable(False, False)
        register_window.configure(bg="#f0f0f0")
        
        # 返回登录窗口的处理
        def back_to_login():
            register_window.destroy()
            login_window.deiconify()
        
        register_window.protocol("WM_DELETE_WINDOW", back_to_login)
        
        # 创建标题
        title_label = tk.Label(register_window, text="注册新账号", font=("SimHei", 16, "bold"), bg="#f0f0f0", fg="#333333")
        title_label.pack(pady=15)
        
        # 创建注册框架
        register_frame = ttk.Frame(register_window, padding="20")
        register_frame.pack(fill=tk.BOTH, expand=True)
        
        # 用户名
        ttk.Label(register_frame, text="用户名:", font=("SimHei", 10)).grid(row=0, column=0, sticky=tk.W, pady=8)
        reg_username_var = tk.StringVar()
        ttk.Entry(register_frame, textvariable=reg_username_var, width=35).grid(row=0, column=1, padx=10, pady=8)
        
        # 邮箱
        ttk.Label(register_frame, text="邮箱:", font=("SimHei", 10)).grid(row=1, column=0, sticky=tk.W, pady=8)
        reg_email_var = tk.StringVar()
        ttk.Entry(register_frame, textvariable=reg_email_var, width=35).grid(row=1, column=1, padx=10, pady=8)
        
        # 密码
        ttk.Label(register_frame, text="密码:", font=("SimHei", 10)).grid(row=2, column=0, sticky=tk.W, pady=8)
        reg_password_var = tk.StringVar()
        ttk.Entry(register_frame, textvariable=reg_password_var, show="*", width=35).grid(row=2, column=1, padx=10, pady=8)
        
        # 确认密码
        ttk.Label(register_frame, text="确认密码:", font=("SimHei", 10)).grid(row=3, column=0, sticky=tk.W, pady=8)
        reg_confirm_password_var = tk.StringVar()
        ttk.Entry(register_frame, textvariable=reg_confirm_password_var, show="*", width=35).grid(row=3, column=1, padx=10, pady=8)
        
        # 注册按钮
        register_button = ttk.Button(register_frame, text="注册", command=lambda: self.register(register_window, login_window, 
                                                                                          reg_username_var, reg_email_var, 
                                                                                          reg_password_var, reg_confirm_password_var))
        register_button.grid(row=4, column=0, columnspan=2, pady=15)
        register_button.configure(width=25)
        
        # 返回登录按钮
        back_button = ttk.Button(register_window, text="返回登录", command=back_to_login)
        back_button.pack(pady=10)
    
    def register(self, register_window, login_window, username_var, email_var, password_var, confirm_password_var):
        """处理注册逻辑"""
        username = username_var.get().strip()
        email = email_var.get().strip()
        password = password_var.get()
        confirm_password = confirm_password_var.get()
        
        # 验证输入
        if not username or not email or not password or not confirm_password:
            messagebox.showerror("错误", "请填写所有必填字段")
            return
        
        if password != confirm_password:
            messagebox.showerror("错误", "两次输入的密码不一致")
            return
        
        if not self.is_valid_email(email):
            messagebox.showerror("错误", "请输入有效的邮箱地址")
            return
        
        # 检查用户名是否已存在
        self.db.cursor.execute("SELECT id FROM users WHERE username=?", (username,))
        if self.db.cursor.fetchone():
            messagebox.showerror("错误", "用户名已存在")
            return
        
        # 检查邮箱是否已存在
        self.db.cursor.execute("SELECT id FROM users WHERE email=?", (email,))
        if self.db.cursor.fetchone():
            messagebox.showerror("错误", "邮箱已被注册")
            return
        
        # 生成验证代码
        verification_code = self.generate_verification_code()
        
        # 保存用户信息到数据库
        password_hash = self.hash_password(password)
        
        try:
            self.db.cursor.execute(
                "INSERT INTO users (username, password_hash, email, verification_code, is_verified) VALUES (?, ?, ?, ?, ?)",
                (username, password_hash, email, verification_code, False)
            )
            self.db.commit()
            
            # 发送验证邮件
            if self.send_verification_email(email, verification_code):
                messagebox.showinfo("注册成功", "注册成功！请检查邮箱中的验证码并进行验证。")
                
                # 显示验证对话框
                self.show_verification_dialog(username, register_window, login_window)
            else:
                # 注册失败，回滚数据
                self.db.rollback()
                messagebox.showerror("注册失败", "发送验证码失败，请稍后重试")
        except Exception as e:
            self.db.rollback()
            messagebox.showerror("注册失败", f"注册过程中发生错误: {str(e)}")
    
    def show_verification_dialog(self, username, register_window, login_window):
        """显示邮箱验证对话框"""
        # 隐藏注册窗口
        register_window.withdraw()
        
        # 创建验证窗口
        verify_window = tk.Toplevel(self.root)
        verify_window.title("邮箱验证")
        verify_window.geometry("400x200")
        verify_window.resizable(False, False)
        verify_window.configure(bg="#f0f0f0")
        
        # 关闭时返回登录窗口
        def on_close():
            verify_window.destroy()
            login_window.deiconify()
        
        verify_window.protocol("WM_DELETE_WINDOW", on_close)
        
        # 创建框架
        verify_frame = ttk.Frame(verify_window, padding="20")
        verify_frame.pack(fill=tk.BOTH, expand=True)
        
        # 验证码输入
        ttk.Label(verify_frame, text="请输入验证码:", font=("SimHei", 10)).grid(row=0, column=0, sticky=tk.W, pady=10)
        code_var = tk.StringVar()
        ttk.Entry(verify_frame, textvariable=code_var, width=20).grid(row=0, column=1, padx=10, pady=10)
        
        # 验证按钮
        def verify_code():
            code = code_var.get().strip()
            
            # 验证验证码
            self.db.cursor.execute("SELECT id, is_verified FROM users WHERE username=? AND verification_code=?", 
                              (username, code))
            user = self.db.cursor.fetchone()
            
            if not user:
                messagebox.showerror("验证失败", "验证码错误")
                return
            
            user_id, is_verified = user
            
            if is_verified:
                messagebox.showinfo("验证成功", "账号已验证，即将返回登录页面")
            else:
                # 更新用户为已验证状态
                self.db.cursor.execute("UPDATE users SET is_verified=?, verification_code=NULL WHERE id=?", 
                                  (True, user_id))
                self.db.commit()
                messagebox.showinfo("验证成功", "邮箱验证成功！请使用账号密码登录")
            
            verify_window.destroy()
            login_window.deiconify()
        
        verify_button = ttk.Button(verify_frame, text="验证", command=verify_code)
        verify_button.grid(row=1, column=0, columnspan=2, pady=15)
        verify_button.configure(width=15)
        
        # 重新发送验证码
        def resend_code():
            new_code = self.generate_verification_code()
            self.db.cursor.execute("UPDATE users SET verification_code=? WHERE username=?", (new_code, username))
            self.db.commit()
            
            # 获取用户邮箱
            self.db.cursor.execute("SELECT email FROM users WHERE username=?", (username,))
            email = self.db.cursor.fetchone()[0]
            
            if self.send_verification_email(email, new_code):
                messagebox.showinfo("重新发送", "新的验证码已发送")
        
        resend_button = ttk.Button(verify_window, text="重新发送验证码", command=resend_code)
        resend_button.pack(pady=5)
    
    def create_forgot_password_window(self, login_window, on_login_success):
        """创建忘记密码窗口"""
        # 创建忘记密码窗口
        forgot_window = tk.Toplevel(self.root)
        forgot_window.title("忘记密码")
        forgot_window.geometry("400x250")
        forgot_window.resizable(False, False)
        forgot_window.configure(bg="#f0f0f0")
        
        # 返回处理
        def back_to_main():
            forgot_window.destroy()
            if login_window:
                login_window.deiconify()
        
        forgot_window.protocol("WM_DELETE_WINDOW", back_to_main)
        
        # 创建标题
        title_label = tk.Label(forgot_window, text="找回密码", font=("SimHei", 16, "bold"), bg="#f0f0f0", fg="#333333")
        title_label.pack(pady=15)
        
        # 创建框架
        forgot_frame = ttk.Frame(forgot_window, padding="20")
        forgot_frame.pack(fill=tk.BOTH, expand=True)
        
        # 邮箱输入
        ttk.Label(forgot_frame, text="请输入注册邮箱:", font=("SimHei", 10)).grid(row=0, column=0, sticky=tk.W, pady=10)
        forgot_email_var = tk.StringVar()
        ttk.Entry(forgot_frame, textvariable=forgot_email_var, width=30).grid(row=0, column=1, padx=10, pady=10)
        
        # 发送验证码按钮
        def send_reset_code():
            email = forgot_email_var.get().strip()
            
            # 验证邮箱格式
            if not self.is_valid_email(email):
                messagebox.showerror("错误", "请输入有效的邮箱地址")
                return
            
            # 检查邮箱是否存在
            try:
                self.db.cursor.execute("SELECT id, username FROM users WHERE email=? AND is_verified=?", (email, True))
                user = self.db.cursor.fetchone()
                
                if not user:
                    messagebox.showinfo("提示", "如果该邮箱已注册，重置链接将发送到您的邮箱")
                    return
                
                user_id, username = user
                
                # 生成重置令牌和过期时间
                reset_token = secrets.token_urlsafe(32)
                expiry = (datetime.now() + timedelta(hours=1)).strftime('%Y-%m-%d %H:%M:%S')
                
                # 保存到数据库
                self.db.cursor.execute("UPDATE users SET reset_token=?, reset_token_expiry=? WHERE id=?", 
                                  (reset_token, expiry, user_id))
                self.db.commit()
                
                # 生成验证码
                verification_code = ''.join([str(secrets.randbelow(10)) for _ in range(6)])
                
                # 尝试保存验证码到数据库，处理可能的列缺失问题
                try:
                    self.db.cursor.execute("UPDATE users SET reset_verification_code=? WHERE id=?", 
                                      (verification_code, user_id))
                    self.db.commit()
                except Exception as e:
                    # 如果出现列缺失错误，重新初始化数据库结构
                    if "no such column" in str(e):
                        self.db.init_database()  # 调用init_database会触发update_database_structure
                        # 再次尝试更新
                        self.db.cursor.execute("UPDATE users SET reset_verification_code=? WHERE id=?", 
                                          (verification_code, user_id))
                        self.db.commit()
                    else:
                        raise e
                
                # 发送重置邮件
                if self.send_reset_email(email, username, verification_code):
                    messagebox.showinfo("验证码已发送", "密码重置验证码已发送，请查收邮件")
                    
                    # 显示重置密码对话框
                    self.show_reset_password_dialog(user_id, email, forgot_window, login_window)
                else:
                    messagebox.showerror("发送失败", "发送验证码失败，请稍后重试")
            except Exception as e:
                messagebox.showerror("错误", f"操作失败: {str(e)}")
        
        send_button = ttk.Button(forgot_frame, text="发送验证码", command=send_reset_code)
        send_button.grid(row=1, column=0, columnspan=2, pady=15)
        send_button.configure(width=20)
        
        # 返回按钮
        back_button = ttk.Button(forgot_window, text="返回", command=back_to_main)
        back_button.pack(pady=10)
    
    def show_reset_password_dialog(self, user_id, email, forgot_window, login_window):
        """显示重置密码对话框"""
        # 隐藏忘记密码窗口
        forgot_window.withdraw()
        
        # 创建重置密码窗口
        reset_window = tk.Toplevel(self.root)
        reset_window.title("重置密码")
        reset_window.geometry("400x300")
        reset_window.resizable(False, False)
        reset_window.configure(bg="#f0f0f0")
        
        # 关闭时返回
        def on_close():
            reset_window.destroy()
            if login_window:
                login_window.deiconify()
            else:
                forgot_window.destroy()
        
        reset_window.protocol("WM_DELETE_WINDOW", on_close)
        
        # 创建框架
        reset_frame = ttk.Frame(reset_window, padding="20")
        reset_frame.pack(fill=tk.BOTH, expand=True)
        
        # 验证码输入
        ttk.Label(reset_frame, text="验证码:", font=("SimHei", 10)).grid(row=0, column=0, sticky=tk.W, pady=10)
        code_var = tk.StringVar()
        ttk.Entry(reset_frame, textvariable=code_var, width=20).grid(row=0, column=1, padx=10, pady=10)
        
        # 新密码
        ttk.Label(reset_frame, text="新密码:", font=("SimHei", 10)).grid(row=1, column=0, sticky=tk.W, pady=10)
        new_password_var = tk.StringVar()
        ttk.Entry(reset_frame, textvariable=new_password_var, show="*", width=20).grid(row=1, column=1, padx=10, pady=10)
        
        # 确认新密码
        ttk.Label(reset_frame, text="确认新密码:", font=("SimHei", 10)).grid(row=2, column=0, sticky=tk.W, pady=10)
        confirm_password_var = tk.StringVar()
        ttk.Entry(reset_frame, textvariable=confirm_password_var, show="*", width=20).grid(row=2, column=1, padx=10, pady=10)
        
        # 重置密码按钮
        def reset_password():
            code = code_var.get().strip()
            new_password = new_password_var.get()
            confirm_password = confirm_password_var.get()
            
            # 验证输入
            if not code or not new_password or not confirm_password:
                messagebox.showerror("错误", "请填写所有必填字段")
                return
            
            if new_password != confirm_password:
                messagebox.showerror("错误", "两次输入的密码不一致")
                return
            
            # 验证验证码和令牌是否有效
            self.db.cursor.execute("SELECT id FROM users WHERE id=? AND reset_verification_code=? AND reset_token_expiry > ?", 
                              (user_id, code, datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
            user = self.db.cursor.fetchone()
            
            if not user:
                messagebox.showerror("错误", "验证码错误或已过期，请重新申请")
                return
            
            # 更新密码，使用系统统一的SHA-256哈希算法
            password_hash = self.hash_password(new_password)
            self.db.cursor.execute("UPDATE users SET password_hash=?, reset_token=NULL, reset_token_expiry=NULL, reset_verification_code=NULL WHERE id=?", 
                              (password_hash, user_id))
            self.db.commit()
            
            messagebox.showinfo("成功", "密码重置成功！")
            reset_window.destroy()
            if login_window:
                login_window.deiconify()
        
        # 重新发送验证码
        def resend_code():
            # 生成新的验证码
            new_code = ''.join([str(secrets.randbelow(10)) for _ in range(6)])
            
            # 发送新的验证码
            if self.send_reset_email(email, "用户", new_code):
                messagebox.showinfo("重新发送成功", "新的验证码已发送，请查收邮件")
        
        reset_button = ttk.Button(reset_frame, text="重置密码", command=reset_password)
        reset_button.grid(row=3, column=0, columnspan=2, pady=15)
        reset_button.configure(width=15)
        
        # 重新发送验证码按钮
        resend_button = ttk.Button(reset_window, text="重新发送验证码", command=resend_code)
        resend_button.pack(pady=5)
        
        # 返回按钮
        back_button = ttk.Button(reset_window, text="返回", command=on_close)
        back_button.pack(pady=5)
        
        reset_button = ttk.Button(reset_frame, text="重置密码", command=reset_password)
        reset_button.grid(row=3, column=0, columnspan=2, pady=15)
        reset_button.configure(width=15)
        
        # 返回按钮
        back_button = ttk.Button(reset_window, text="返回", command=on_close)
        back_button.pack(pady=5)